/* 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

Project: MyMANET - A Platform to Build Customized MANET's
Developers: Ashwin Paranjpe, Santosh Vempala
Georgia Institute of Technology, Atlanta, USA
 */

#ifndef NEW_KERNEL	/*Parameter to Makefile*/
#define NEW_KERNEL 1	/*Default Value*/
#endif

#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/sched.h>
#include<linux/module.h>
#include<linux/moduleparam.h>
#include<linux/netdevice.h>
#include<linux/etherdevice.h>
#if NEW_KERNEL
#include<net/net_namespace.h>
#endif
#include<linux/proc_fs.h>
#include<linux/ip.h>
#include<net/ip.h>
#include<linux/string.h>
#include<linux/list.h>
#include<linux/timer.h>

#include "main.h"
#include "string_functions.h"
#include "proc_functions.h"
#include "distance_list_functions.h"
#include "stat_list_functions.h"
#include "timer_functions.h"


MODULE_LICENSE("GPL");


/*Start of Manifold Module Code*/

module_param(device_name, charp, S_IRUGO);	/*Device name can be given as a parameter to the module*/

struct mymanet_header{
	uint8_t session_id;
	uint8_t hops_remaining;
	uint8_t distance;
	uint8_t original_source[6];
	uint8_t final_destination[6];
	uint8_t eth_type_original[2];
};


/*This is not a standalone function and shall be called from wdl_handle_receive
 * function ONLY when there is a need to change the MAC header of the packet.
 * This function simply makes the necessary changes in the packet and then 
 * calls the wdl_hard_start_xmit function which will then do the actual work 
 * of transmission. Currently it retransmits the broadcast packet by spoofing
 * its own MAC as the source MAC
 * */
int wdl_modify_and_transmit(struct sk_buff *skb, struct net_device *netdev, uint8_t dest_mac[6], uint8_t src_mac[6], struct mymanet_header mymanethdr)
{

	int orig_header_room=0;

	uint8_t eth_type_manifold[2];
	eth_type_manifold[0] = 0x33;
	eth_type_manifold[1] = 0x33;


	orig_header_room = (int)(skb->data - skb->head);
	if(orig_header_room < (ETHERNET_HEADER_SIZE + MANIFOLD_HEADER_SIZE)){
		printk(KERN_ALERT"\nSTRANGE : Insufficient headroom for constructing Manifold Packet\n");
		kfree_skb(skb);
		return -1;
	}

	memcpy(skb_push(skb, MANIFOLD_HEADER_SIZE), &mymanethdr, MANIFOLD_HEADER_SIZE);

	memcpy(skb_push(skb, 2), eth_type_manifold, 2);
	memcpy(skb_push(skb, 6), src_mac, 6);
	memcpy(skb_push(skb, 6), dest_mac, 6);


#if DEBUG_ON
	printk(KERN_ALERT "\nMODIFIED and TRANSMITTED pkt from  %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x type %x:%x", 
			src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5],
			dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5], 
			eth_type_manifold[0], eth_type_manifold[1]);
#endif

	if(netdev->flags & IFF_UP)
		return (dev_queue_xmit(skb));
	else{
		printk(KERN_ALERT "\n\nDevice was DOWN !! \n\n");
		return NETDEV_TX_BUSY;	
	}
}


/*This is the primary receive function for the newly defined packet type
 *It is assumed that when we arrive at this function, the skb->data points to the start of 
 *the payload of the packet after the MAC header
 *It internally calls the function eth_type_trans which actually classifies the packet
 *as being destined to the host, broadcast and so on
 *Based on information returned from eth_type_trans, this function will then re-transmit
 *if the packet is broadcast or destined to some other host.
 **/
int wdl_handle_recieve(struct sk_buff *skb, struct net_device *netdev, struct packet_type *ptype, struct net_device *orig_dev)
{
	uint8_t dest_mac[6];
	uint8_t src_mac[6];
	uint8_t eth_type[2];
	struct mymanet_header mymanethdr;

	uint8_t flag=0;

	uint8_t distance_from_me = 0;

	int orig_header_room=0;

	if((skb == NULL) || (netdev == NULL) || (ptype == NULL) || (orig_dev == NULL)){
		if(skb != NULL){
			kfree_skb(skb);
		}
		return 0;
	}

	orig_header_room = (int)(skb->data - skb->head);

	if((orig_header_room) < (ETHERNET_HEADER_SIZE)){
		printk(KERN_ALERT"\nlen b4 data is just : %d\n",orig_header_room);
		if(skb != NULL){
			kfree_skb(skb);
		}
		return 0;
	}

	if((global_recieved_count++) == 10)
		printk(KERN_ALERT "\nRECIEVED 10 pkts on device %s", d->name);

	/*Since we are at the start of the payload after Eth header, go back and fetch the info.*/
	memcpy(dest_mac, ( (skb->data - (2 * 6)) - 2 ), 6);
	memcpy(src_mac, ( (skb->data - 6) - 2 ), 6);
	memcpy(eth_type, ( skb->data - 2 ), 2);

	memcpy(&mymanethdr, skb->data, MANIFOLD_HEADER_SIZE);

#if DEBUG_ON
	printk(KERN_ALERT"\nReceived Session ID as %d\n", mymanethdr.session_id);
	printk(KERN_ALERT"\nReceived Hops Remaining as %d\n", mymanethdr.hops_remaining);
	printk(KERN_ALERT"\nReceived Distance as %d\n", mymanethdr.distance);
	printk(KERN_ALERT"\nReceived Original Source MAC as %x:%x:%x:%x:%x:%x\n", mymanethdr.original_source[0], mymanethdr.original_source[1], 
		mymanethdr.original_source[2], mymanethdr.original_source[3], mymanethdr.original_source[4], mymanethdr.original_source[5]);
	printk(KERN_ALERT"\nReceived Final Dest MAC as %x:%x:%x:%x:%x:%x\n", mymanethdr.final_destination[0], mymanethdr.final_destination[1], 
		mymanethdr.final_destination[2], mymanethdr.final_destination[3], mymanethdr.final_destination[4], mymanethdr.final_destination[5]);
	printk(KERN_ALERT"\nReceived Original Type as %x:%x\n", mymanethdr.eth_type_original[0], mymanethdr.eth_type_original[1]);
#endif


	/*Do skb_pull of MANIFOLD_HEADER_SIZE*/
	skb_pull(skb, MANIFOLD_HEADER_SIZE);
	/*End: Extraction of Manifold Header fields ends here*/


	/*eth_type_trans will understand only the original eth_type numbers*/
	/*Build a normal ethernet layer packet header so that eth_type_trans can synthesize it.*/
	memcpy(skb_push(skb, 2), mymanethdr.eth_type_original, 2 );
	memcpy(skb_push(skb, 6), mymanethdr.original_source, 6);
	memcpy(skb_push(skb, 6), mymanethdr.final_destination, 6);

	skb->pkt_type = PACKET_HOST; /* Assumed. eth_type_trans will overwrite it anyways.*/
	/*Remember that eth_type_trans function does skb_pull of length ETH_HLEN. 
	 * So the newly created Eth header will be stripped off
	 * */
	skb->protocol = eth_type_trans(skb, netdev);

#if DEBUG_ON
	if((global_recieved_count)<10){
		printk(KERN_ALERT"\nlen b4 data is just : %d",(int)(skb->data - skb->head));
		printk(KERN_ALERT "\nRECIEVED pkt from  %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x eth_type %x:%x\n", 
				src_mac[0],src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5],
				dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5], 
				eth_type[0], eth_type[1]);
	}
#endif

	if((memcmp(mymanethdr.original_source, netdev->dev_addr, 6) != 0)){
		/*Add or Update this MAC entry to our Stat List*/
		add_or_update_stat_entry(mymanethdr.original_source, 0, mymanethdr.session_id);
	}


	/*Start of Actual Processing*/
	/*No Need to do SKB expansion since this is a Manifold packet which has already been expanded
	 * to have sufficient head-room*/

	if (skb->pkt_type == PACKET_HOST) {

		netif_rx(skb);
		return 0;
	}
	else if ((skb->pkt_type == PACKET_BROADCAST) || (skb->pkt_type == PACKET_OTHERHOST)) {
		/*We need to forward such packets after making some changes i.e. broadcast retransmit*/

#if DEBUG_ON
		printk(KERN_ALERT "\nRECIEVED broadcast pkt from  %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x eth_type %x:%x", 
				src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5], 
				dest_mac[0], dest_mac[1], dest_mac[2], dest_mac[3], dest_mac[4], dest_mac[5], 
				eth_type[0], eth_type[1]);
#endif
		g_manifold_recieved_count++;

		/*Get my distance from this final destination*/
		distance_from_me = search_for_distance(mymanethdr.final_destination);

		/*Reduce the Number of Remaining Hops for this packet*/
		mymanethdr.hops_remaining = mymanethdr.hops_remaining - 1;

		if((!global_manifold_disable) && ((distance_from_me < mymanethdr.distance) || (distance_from_me == 0xFF)) 
				&& (distance_from_me != 0) && (mymanethdr.hops_remaining != 0)
				&& (memcmp(mymanethdr.final_destination, netdev->dev_addr, 6) != 0)) { 

			mymanethdr.distance = distance_from_me;
			wdl_modify_and_transmit((skb_copy(skb, GFP_ATOMIC)), netdev, g_broadcast_mac, netdev->dev_addr, mymanethdr);

		}

		if(skb->pkt_type == PACKET_BROADCAST){
			flag = 1;
			netif_rx(skb); /*This is originally a Broadcast packet. For E.g. Heart-Beats*/
		}
		if((flag != 1) && (skb != NULL)){
			kfree_skb(skb);
		}

		return 0;
	}
	else{	/*Packet is neither broadcast nor unicast. In the case of MyMANET, it appears to be be a garbled packet. Free the skb.*/
		if(skb != NULL){
			kfree_skb(skb);
		}
		return 0;	
	}
}


/*This function will do the actual job of transmitting the packet through this module
 * It calls the lower level hard_start_xmit function which was earlier saved during the module initialization*/
int wdl_hard_start_xmit(struct sk_buff *skb_original, struct net_device *netdev)
{
	uint8_t eth_type_manifold[2];
	int orig_header_room = 0, new_header_room = 0;
	struct sk_buff *skb = NULL;
	struct mymanet_header mymanethdr;

	eth_type_manifold[0]=0x33;
	eth_type_manifold[1]=0x33;

	if((device_hard_start_xmit_backup != NULL) && (netdev != NULL)){

		g_manifold_transmitted_count++;

		memcpy(mymanethdr.final_destination, skb_original->data, 6);
		memcpy(mymanethdr.original_source, skb_original->data + 6, 6);
		memcpy(mymanethdr.eth_type_original, skb_original->data + 12, 2);

		/*CHECK whether this is a Manifold packet
		 *coming from the reception path */
		if((mymanethdr.eth_type_original[0] == eth_type_manifold[0]) && (mymanethdr.eth_type_original[1] == eth_type_manifold[1])){

			/*This is a Manifold packet coming from our Reception Path.
			 * Dont touch this packet. Just send it as it is.
			 * */
#if DEBUG_ON
			if(global_transmitted_count < 10){
				printk(KERN_ALERT "\n Rx->Tx : TRANSMITTED pkt from  %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x eth_type: %x:%x", 
						mymanethdr.original_source[0], mymanethdr.original_source[1], mymanethdr.original_source[2], 
						mymanethdr.original_source[3], mymanethdr.original_source[4], mymanethdr.original_source[5], 
						mymanethdr.final_destination[0], mymanethdr.final_destination[1], 
						mymanethdr.final_destination[2], mymanethdr.final_destination[3], 
						mymanethdr.final_destination[4], mymanethdr.final_destination[5],
						eth_type_manifold[0], eth_type_manifold[1]);
			}
#endif

			/*Actual Transmission happens here*/
			if(netdev->flags & IFF_UP)
				return (*device_hard_start_xmit_backup)(skb_original, netdev);
			else{
				printk(KERN_ALERT "\n\nDevice was DOWN !! \n\n");
				return NETDEV_TX_BUSY;	
			}

		}
		else{
			/*This is NOT a Manifold Packet.
			 * Convert it into a Manifold Packet.
			 * */

			global_transmitted_count++;

			g_manifold_session_transmitted_count++;

			/*Add or Update this MAC entry in our Stat List*/
			//TODO: Currently commented because Broadcast MAC gets added to Stat List due to
			//Broadcast transmissions
			//add_or_update_stat_entry(dest_mac, 1, 0);

			orig_header_room = (int)(skb_original->data - skb_original->head);

			/*Here add the header room required for the new fields in Manifold packet*/
			skb = skb_copy_expand(skb_original, (orig_header_room + MANIFOLD_HEADER_SIZE), 0, GFP_ATOMIC);

			skb_pull(skb, ETHERNET_HEADER_SIZE);

			/*Get the distance for this final Destination*/
			mymanethdr.distance = search_for_distance(mymanethdr.final_destination);

			mymanethdr.hops_remaining = MAX_HOPS;

			mymanethdr.session_id = g_session_id;

			memcpy(skb_push(skb, MANIFOLD_HEADER_SIZE), &mymanethdr, MANIFOLD_HEADER_SIZE);

			/*Spoof our own address as source address and Dest MAC = Broadcast*/
			memcpy(skb_push(skb, 2), eth_type_manifold, 2 );
			memcpy(skb_push(skb, 6), netdev->dev_addr, 6);
			memcpy(skb_push(skb, 6), g_broadcast_mac, 6);


			new_header_room = (int)(skb->data - skb->head);

#if DEBUG_ON
			if(global_transmitted_count < 10){
				printk(KERN_ALERT "\n Tx->Tx: Original header length :%d New : %d TRANSMITTED pkt from  %x:%x:%x:%x:%x:%x to %x:%x:%x:%x:%x:%x eth_type: %x:%x orig_eth_type %x:%x\n", orig_header_room, 
					new_header_room, netdev->dev_addr[0], netdev->dev_addr[1], netdev->dev_addr[2], netdev->dev_addr[3], 
					netdev->dev_addr[4], netdev->dev_addr[5], g_broadcast_mac[0], g_broadcast_mac[1], g_broadcast_mac[2], 
					g_broadcast_mac[3], g_broadcast_mac[4], g_broadcast_mac[5], eth_type_manifold[0], eth_type_manifold[1], 
					mymanethdr.eth_type_original[0], mymanethdr.eth_type_original[1]);
			}
#endif


			kfree_skb(skb_original);

			/*Actual Transmission happens here*/
			if(netdev->flags & IFF_UP)
				return (*device_hard_start_xmit_backup)(skb, netdev);
			else{
				printk(KERN_ALERT "\n\nDevice was DOWN !! \n\n");
				return NETDEV_TX_BUSY;	
			}
		}
	}
	kfree_skb(skb_original);
	return 0;
}


/*Basic Module specific code starts here*/

/* This function is called everytime when the module is un-loaded
 * It re-instates the hard_start_xmit function to the lower level function and 
 * deletes the proc entry as well as removes the new registered packet type*/
static void hello_exit(void)
{
	mod_timer(&g_session_timer, jiffies + 1);
	del_timer_sync(&g_session_timer);

	if((device_hard_start_xmit_backup != NULL) && (d !=NULL))
		d->hard_start_xmit = device_hard_start_xmit_backup;

	if(d != NULL)
		dev_put(d);

	dev_remove_pack(&manifold_ptype);

	remove_proc_entry("wdl", NULL);
	remove_proc_entry("rxstats", NULL);
	remove_proc_entry("txstats", NULL);

	free_entire_distance_list();
	free_entire_stat_list();

	printk(KERN_ALERT "\nByee! nice world\n");
}

/*This is the MAIN module initialization function for the module.
 *It's called everytime we try to load the module
 *It creates a new proc entry and also registers a new packet type
 *It grabs the main DEVICE structure(net_device) for the interface specified in the module.
 *It then saves the lower level transmit function in a function pointer for later use; and then
 *replaces it with the module's own transmit function i.e. wdl_hard_start_xmit
 * */
static int hello_init(void)
{

	struct proc_dir_entry *proc_entry, *proc_entry_rxstats, *proc_entry_txstats, *proc_entry_distance;


	dev_add_pack(&manifold_ptype);

	proc_entry = create_proc_entry("wdl", 0444, NULL);
	if (proc_entry == NULL) {
		hello_exit();
		return -ENOMEM;
	} 
	else {
		proc_entry->read_proc = wdl_read;
		proc_entry->write_proc = wdl_write;
		proc_entry->owner = THIS_MODULE;
		printk(KERN_ALERT "ash: proc entry created\n");
	}

	proc_entry_rxstats = create_proc_entry("rxstats", 0444, NULL);
	if (proc_entry_rxstats == NULL) {
		hello_exit();
		return -ENOMEM;
	} 
	else {
		proc_entry_rxstats->read_proc = rxstats_read;
		proc_entry_rxstats->write_proc = NULL;
		proc_entry_rxstats->owner = THIS_MODULE;
		printk(KERN_ALERT "ash: rxstats proc entry created\n");
	}

	proc_entry_txstats = create_proc_entry("txstats", 0444, NULL);
	if (proc_entry_txstats == NULL) {
		hello_exit();
		return -ENOMEM;
	} 
	else {
		proc_entry_txstats->read_proc = txstats_read;
		proc_entry_txstats->write_proc = NULL;
		proc_entry_txstats->owner = THIS_MODULE;
		printk(KERN_ALERT "ash: txstats proc entry created\n");
	}

	proc_entry_distance = create_proc_entry("distance", 0444, NULL);
	if (proc_entry_distance == NULL) {
		hello_exit();
		return -ENOMEM;
	} 
	else {
		proc_entry_distance->read_proc = distance_read;
		proc_entry_distance->write_proc = NULL;
		proc_entry_distance->owner = THIS_MODULE;
		printk(KERN_ALERT "ash: distance proc entry created\n");
	}



	printk(KERN_ALERT "Hello, world. the current process is: %s pid is : %d module param is: %s \n", current->comm, 
			current->pid, device_name);

	initialize_session_timer();

	if(strncmp(device_name, "", strlen(device_name)) == 0){
		printk(KERN_ALERT"\nDevice Name Not specified. Cannot load module. Try again.\n");
		hello_exit();
		return 0;
	}
#if NEW_KERNEL
	d = dev_get_by_name(&init_net, device_name);
#else
	d = dev_get_by_name(device_name);
#endif
	if(d != NULL)
		printk(KERN_ALERT "Yeah !! name of device is %s  irq number is : %d flags %d type %d", d->name, 
				d->irq, d->flags, d->type);
	else
		hello_exit();

	device_hard_start_xmit_backup = d->hard_start_xmit;
	d->hard_start_xmit = wdl_hard_start_xmit;


	return 0;
}

module_init(hello_init);
module_exit(hello_exit);

